﻿using System;
using Fine.Collections.Intf;
using Fine.Utils.Functions;
using JetBrains.Annotations;



namespace Fine.Collections.Const
{
    /// <summary>
    /// Tiny immutable set. 
    /// A set of a few number of object that contains in an array.
    /// It also maintains the order of items given via constructor.
    /// Don't requires from object to be <c>Comparable</c>.
    /// </summary>
    public sealed class ConstTinySet<T> : ConstSet<T>, RSet<T>
    {


        #region CONSTRUCTORS

        internal struct ConstructionData
        {
            internal int firstIndex;
            internal T[] arr; 
            internal int count; 
        }


        internal ConstTinySet(int firstIndex, T[] array, int count, bool removeDuplicates, bool takeOwnership)
            : this(PrepareData(firstIndex, array, count,
                               removeDuplicates, takeOwnership))
        { }


        private ConstTinySet(ConstructionData cdata)
            : base(cdata.firstIndex, cdata.arr, cdata.count, true)
        { }



        private static ConstructionData PrepareData(int firstIndex, T[] array, int count, bool removeDuplicates, bool takeOwnership)
        {
            var cdata = new ConstructionData
                        {
                            firstIndex = firstIndex, 
                            arr = array,
                            count = count
                        };

            if (!takeOwnership)
            {
                cdata.arr = new T[cdata.count];
                Array.Copy(array, cdata.arr, cdata.count);
            }

            if (removeDuplicates)
                RemoveDuplicates(cdata.arr, ref cdata.count);

            return cdata;
        }


        private static void RemoveDuplicates(T[] array, ref int count)
        {
            if (count < 2)
                return;

            for (int k = count-1; k > 0; k--) // sic! - from n's downto 2
            {
                bool exists = false;
                for (int i = k-1; i >= 0; i--)
                    if (equator.Equals(array[k], array[i]))
                    {
                        exists = true;
                        break;
                    }

                if (exists)
                {
                    array.ExcludeAt(k, ref count);
                    continue;
                }
            }
        }

        #endregion



        #region IMPLEMENTED ABSTRSCT METHODS

        public override int IndexOf(T item)
        {
            for (int i = 0; i < count; i++)
                if (equator.Equals(arr[i], item))
                    return firstIndex + i;
            return int.MinValue;
        }

        #endregion



        /// <summary>
        /// Returns a set that contains all items of the source <c>set</c> plus one additional <c>item</c>.
        /// If the source <c>set</c> already contains the <c>item</c> - the source <c>set</c> is returned.
        /// </summary>
        /// <param name="set">the source set.</param>
        /// <param name="item">the additional item.</param>
        /// <returns>a result set.</returns>
        public static ConstTinySet<T> operator + (ConstTinySet<T> set, T item)
        {
            if (set == null)
            {
                return CC1.TinySetOf(item);
            }
            else if (set.Contains(item))
            {
                return set;
            }
            else
            {
                var cdata = new ConstructionData
                            {
                                firstIndex = set.firstIndex,
                                arr = new T[set.count + 1],
                                count = set.count + 1
                            };
                Array.Copy(set.arr, cdata.arr, set.count);
                cdata.arr[cdata.count-1] = item;
                return new ConstTinySet<T>(cdata);
            }
        }


        /// <summary>
        /// Returns a set that contains item of the source <c>set</c>
        /// excluding the specified <c>item</c>.
        /// If the source <c>set</c> doesn't contain the specified <c>item</c>,
        /// the source <c>set</c> is returned.
        /// </summary>
        /// <param name="set">the source set.</param>
        /// <param name="item">the item to exclude.</param>
        /// <returns>a result set.</returns>
        public static ConstTinySet<T> operator - (ConstTinySet<T> set, T item)
        {
            if (set == null)
                return null;

            int index = Array.IndexOf(set.arr, item, 0, set.count);
            if (index >= 0)
            {
                int n = set.count;
                if (n > 1)
                {
                    int m = n - 1;
                    var cdata = new ConstructionData
                                {
                                    firstIndex = set.firstIndex,
                                    arr = new T[m],
                                    count = m
                                };
                    if (index > 0)
                        Array.Copy(set.arr, cdata.arr, index);
                    if (index < m)
                        Array.Copy(set.arr, index+1, cdata.arr, index, m-index);
                    return new ConstTinySet<T>(cdata);
                }
                else
                {
                    return new ConstTinySet<T>(new ConstructionData { firstIndex = set.firstIndex, arr = emptyArray, count = 0 });
                }
            }
            else
            {
                return set;
            }
        }


        [NotNull]
        private static readonly T[] emptyArray = new T[0];
    }

}
